#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

void computeHistogram(const cv::Mat& frame, cv::Mat& hist, int channel ){
    // Compute histogram
    int histSize = 256; // Number of bins of histogram
    float range[] = {0, 256}; // Pixel value range
    const float* histRange = {range};
    cv::calcHist(&frame, 1, &channel, cv::Mat(), hist, 1, &histSize, &histRange);

    // Normalize histogram
    cv::normalize(hist, hist, 0, 400, cv::NORM_MINMAX);
}

int main() {
    // Open camera
    cv::VideoCapture input_stream(0);
    if (!input_stream.isOpened()) {
        std::cerr << "Could not open camera\n";
        return EXIT_FAILURE;
    }

    // Create window
    const std::string original_window = "Original Image";
    const std::string gray_window = "Grayscale Image";
    const std::string gray_hist_window = "Grayscale Histogram";
    const std::string bgr_hist_window = "BGR Histogram";
    cv::namedWindow(original_window, cv::WINDOW_NORMAL);
    cv::namedWindow(gray_window, cv::WINDOW_NORMAL);
    cv::namedWindow(gray_hist_window, cv::WINDOW_NORMAL);
    cv::namedWindow(bgr_hist_window, cv::WINDOW_NORMAL);

    cv::Mat frame, gray_frame, hist;
    while (true) {
        // Capture frame
        input_stream >> frame;
        if (frame.empty()) {
            break;
        }
        // Convert to grayscale
        cv::cvtColor(frame, gray_frame, cv::COLOR_BGR2GRAY);

        // Compute grayscale histogram 
        computeHistogram(gray_frame, hist, 0);
        // Create grayscale histogram image
        int hist_w = 512, hist_h = 400;
        cv::Mat histImage(hist_h, hist_w, CV_8UC3, cv::Scalar(0, 0, 0));
        // Draw grayscale histogram
        int bin_w = cvRound((double)hist_w / 256); // Width of each bin
        for (int i = 1; i < 256; i++) {
            cv::line(histImage, 
                     cv::Point(bin_w * (i - 1), hist_h - cvRound(hist.at<float>(i - 1))), 
                     cv::Point(bin_w * i, hist_h - cvRound(hist.at<float>(i))), 
                     cv::Scalar(255, 255, 255), 2, 8, 0);
        }

        // Compute BGR histogram
        cv::Mat b_hist, g_hist, r_hist;
        computeHistogram(frame, b_hist, 0);
        computeHistogram(frame, g_hist, 1);
        computeHistogram(frame, r_hist, 2);
        // Create BGR histgram image
        cv::Mat bgrHistImage(hist_h, hist_w, CV_8UC3, cv::Scalar(0, 0, 0));
        // Draw BGR histograms
        for (int i = 1; i<256; i++){
            // Blue channel
            cv::line(bgrHistImage, 
                cv::Point(bin_w * (i-1), hist_h - cvRound(b_hist.at<float>(i-1))),
                cv::Point(bin_w * i, hist_h - cvRound(b_hist.at<float>(i))),
                cv::Scalar(255, 0, 0), 2, 8, 0);
            // Green channel
            cv::line(bgrHistImage, 
                cv::Point(bin_w * (i-1), hist_h - cvRound(g_hist.at<float>(i-1))),
                cv::Point(bin_w * i, hist_h - cvRound(g_hist.at<float>(i))),
                cv::Scalar(0, 255, 0), 2, 8, 0);
            cv::line(bgrHistImage, 
                cv::Point(bin_w * (i-1), hist_h - cvRound(r_hist.at<float>(i-1))),
                cv::Point(bin_w * i, hist_h - cvRound(r_hist.at<float>(i))),
                cv::Scalar(0, 0, 255), 2, 8, 0);
            
        }
        // Display images
        cv::imshow(original_window, frame);
        cv::imshow(gray_window, gray_frame);
        cv::imshow(gray_hist_window, histImage);
        cv::imshow(bgr_hist_window, bgrHistImage);

        // Press Esc key to exit
        if (cv::waitKey(15) == 27) {
            break;
        }
    }

    // Release resources
    input_stream.release();
    cv::destroyAllWindows();

    return 0;
}